package com.credithc.eagleeye.plugins.mvc;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.EagleEyeConstant;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.exception.EagleEyeIgnoreException;
import com.credithc.eagleeye.plugins.util.IOUtil;
import com.credithc.eagleeye.plugins.util.IpUtil;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.FastJsonConvert;
import com.credithc.eagleeye.util.ThrowableToStringArray;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
/**
 * 描 述: 鹰眼mvc拦截器<br/>
 * 作 者: zhangjin<br/>
 * 创 建：2017年05月26日<br/>
 * 版 本：v1.0.0<br>
 * <p>
 * 历 史: (版本) 作者 时间 注释 <br/><br/>
 * 从1.0.9版本开始，改用aop方式实现(在starter中已经默认启用，已经使用AbstractEagleEyeMvcInterceptor的项目可以手工移除拦截器配置，防止重复收集调用链信息）
 * @see EagleEyeMvcAspect
 */
@SuppressWarnings("Duplicates")
@Deprecated
public abstract class AbstractEagleEyeMvcInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger= LoggerFactory.getLogger(AbstractEagleEyeMvcInterceptor.class);
    private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
    private static final String EAGLE_EYE_THREAD_LOCAL_KEY_MVC = "mvcRpcTraceInfoVo";
    private static final String STOPWATCH_KEY_MVC = "mvc";
    private static int MAX_REQUEST_BODY_LENGTH=4000;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        this.initEagleEyeData(request,handler);
        return super.preHandle(request, response, handler);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 发送鹰眼数据
        finishEagleEyeData(request,response,handler, ex);
        super.afterCompletion(request, response, handler, ex);
    }
    /**
     * 获取用户信息
     * @param request
     * @return
     */
    protected abstract Map<String,String> getUserInfo(HttpServletRequest request);

    /**
     * 初始化鹰眼数据
     * @param request
     * @param handler
     */
    private void initEagleEyeData(HttpServletRequest request,Object handler) {
        EagleEyeThreadContext.init();
        EagleEyeThreadContext.startWatch(STOPWATCH_KEY_MVC);
        TraceInfo traceInfo = new TraceInfo();
        traceInfo.setUserInfo(getUserInfo(request));
        String hcTraceId = request.getHeader(EagleEyeConstant.HC_TRACE_ID);
        if(StringUtils.isNotBlank(hcTraceId)) {
            traceInfo.setTraceId(hcTraceId);
        }else{
            //为了兼容credithc-framework-log模块中的trace
            String mdcTraceId = MDC.get(EagleEyeConstant.TRACE_ID);
            if (StringUtils.isNotBlank(mdcTraceId)) {
                traceInfo.setTraceId(mdcTraceId);
            }
        }

        String hcRpcId = request.getHeader(EagleEyeConstant.HC_RPC_ID);
        if (hcRpcId != null && hcRpcId.length() > 0) {
            traceInfo.setRpcId(hcRpcId);
            traceInfo.addHierarchy();
            traceInfo.addSequenceNo();
        }

        if(handler!=null && (handler instanceof HandlerMethod)){
            HandlerMethod handlerMethod=(HandlerMethod)handler;
            RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();
            rpcTraceInfoVo.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
            rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
            rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
            rpcTraceInfoVo.setRpcType(RpcTypeEnum.HTTP.getRpcName());
            rpcTraceInfoVo.setServiceCategory("mvc");
            rpcTraceInfoVo.setServiceName(handlerMethod.getBeanType().getSimpleName());
            rpcTraceInfoVo.setMethodName(handlerMethod.getMethod().getName());
            if(EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter()) {
                Map<String, String[]> parameterMap = request.getParameterMap();
                StringBuilder requestBodySb = new StringBuilder();
                if((parameterMap!=null&&parameterMap.size()>0)) {
                    requestBodySb.append("parameterMap:");
                    requestBodySb.append(FastJsonConvert.convertObjectToJSON(parameterMap));
                    requestBodySb.append(";");
                }
                //读取body，但是必须在过滤器里替换
                try {
                    if (request instanceof ContentCachingRequestWrapper) {
                        String body = IOUtil.toString(request.getInputStream(), "UTF-8");
                        if (body != null && body.length() > 0) {
                            requestBodySb.append("body:");
                            requestBodySb.append(body);
                            rpcTraceInfoVo.setRequestSize(requestBodySb.length());
                        }
                    }
                } catch (IOException e) {
                }
                if(requestBodySb.length()<=MAX_REQUEST_BODY_LENGTH) {
                    rpcTraceInfoVo.setRequestJson(requestBodySb.toString());
                }else{
                    rpcTraceInfoVo.setRequestJson(requestBodySb.substring(0, MAX_REQUEST_BODY_LENGTH)+"..");
                }
            }
            rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());
            rpcTraceInfoVo.setServerHost(HostUtil.getIp()+":"+request.getServletPath());

            String ip = StringUtils.substringBefore(request.getHeader("x-forwarded-for"),",");
            if(StringUtils.isBlank(ip)) {
                ip = request.getRemoteAddr();
            }
            rpcTraceInfoVo.setClientHost(ip);
            EagleEyeThreadContext.putContext(EAGLE_EYE_THREAD_LOCAL_KEY_MVC, rpcTraceInfoVo);
        }

        traceInfo.addHierarchy();
        EagleEyeThreadContext.putTraceInfo(traceInfo);
        MDC.put("traceId", traceInfo.getTraceId());
        MDC.put("rpcId", traceInfo.getRpcId());
        MDC.put("clientIp", IpUtil.getClientIp(request));
    }

    /**
     * 发送鹰眼数据
     * @param request
     * @param ex
     */
    private void finishEagleEyeData(HttpServletRequest request, HttpServletResponse response,Object handler,Exception ex) {
        try {
            RpcTraceInfoVo rpcTraceInfoVo =  EagleEyeThreadContext.getContext(EAGLE_EYE_THREAD_LOCAL_KEY_MVC);
            if (rpcTraceInfoVo != null) {
                rpcTraceInfoVo.setRunTime(EagleEyeThreadContext.stopWatch(STOPWATCH_KEY_MVC));
                if (ex != null && !(ex instanceof EagleEyeIgnoreException)) {
                    //ERROR
                    rpcTraceInfoVo.setResult("ERROR");
                    //异常的结果
                    String[] traceStr = ThrowableToStringArray.convert(ex);
                    StringBuilder builder = new StringBuilder();
                    for(String trace : traceStr) {
                        builder.append(trace);
                        builder.append("\n");
                    }
                    rpcTraceInfoVo.setResponseJson(builder.toString());
                } else {
                    //OK
                    rpcTraceInfoVo.setResult("OK");
                }

                EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
            }
        } catch (Exception e) {
        }finally {
            EagleEyeThreadContext.remove(EAGLE_EYE_THREAD_LOCAL_KEY_MVC);
            EagleEyeThreadContext.removeTraceInfo();
            MDC.remove("traceId");
            MDC.remove("rpcId");
            MDC.remove("clientIp");
        }
    }

}
